/***************************************************************************************
  * 本程序由江协科技创建并免费开源共享
  * 你可以任意查看、使用和修改，并应用到自己的项目之中
  * 程序版权归江协科技所有，任何人或组织不得将其据为己有
  * 
  * 程序名称：				0.96寸OLED显示屏驱动程序（4针脚I2C接口）
  * 程序创建时间：			2023.10.24
  * 当前程序版本：			V1.2
  * 当前版本发布时间：		2024.4.24
  * 
  * 江协科技官方网站：		jiangxiekeji.com
  * 江协科技官方淘宝店：	jiangxiekeji.taobao.com
  * 程序介绍及更新动态：	jiangxiekeji.com/tutorial/oled.html
  * 
  * 如果你发现程序中的漏洞或者笔误，可通过邮件向我们反馈：feedback@jiangxiekeji.com
  * 发送邮件之前，你可以先到更新动态页面查看最新程序，如果此问题已经修改，则无需再发邮件
  ***************************************************************************************
  */

#include "OLED.h"

/**
  * 数据存储格式：
  * 纵向8点，高位在下，先从左到右，再从上到下
  * 每一个Bit对应一个像素点
  * 
  *      B0 B0                  B0 B0
  *      B1 B1                  B1 B1
  *      B2 B2                  B2 B2
  *      B3 B3  ------------->  B3 B3 --
  *      B4 B4                  B4 B4  |
  *      B5 B5                  B5 B5  |
  *      B6 B6                  B6 B6  |
  *      B7 B7                  B7 B7  |
  *                                    |
  *  -----------------------------------
  *  |   
  *  |   B0 B0                  B0 B0
  *  |   B1 B1                  B1 B1
  *  |   B2 B2                  B2 B2
  *  --> B3 B3  ------------->  B3 B3
  *      B4 B4                  B4 B4
  *      B5 B5                  B5 B5
  *      B6 B6                  B6 B6
  *      B7 B7                  B7 B7
  * 
  * 坐标轴定义：
  * 左上角为(0, 0)点
  * 横向向右为X轴，取值范围：0-127
  * 纵向向下为Y轴，取值范围：0-63
  * 
  *       0             X轴           127 
  *      .------------------------------->
  *    0 |
  *      |
  *      |
  *      |
  *  Y轴 |
  *      |
  *      |
  *      |
  *   63 |
  *      v
  * 
  */


/*全局变量*********************/

/**
  * OLED显存数组
  * 所有的显示函数，都只是对此显存数组进行读写
  * 随后调用OLED_Update函数或OLED_UpdateArea函数
  * 才会将显存数组的数据发送到OLED硬件，进行显示
  */
uint8_t OLED_DisplayBuf[OLED_WIDTH*OLED_HEIGHT/8];
uint8_t cursortop=0;
// //I2C总线句柄
// i2c_master_bus_handle_t oled_bus_handle;

// //OLED设备句柄
// i2c_master_dev_handle_t oled_dev_handle;


/*********************全局变量*/

/*通信协议*********************/

/**
  * 函    数：OLED写命令
  * 参    数：Command 要写入的命令值，范围：0x00-0xFF
  * 返 回 值：无
  */

/**
  * 函    数：OLED写命令
  * 参    数：Command 要写入的命令
  * 返 回 值：无
  */
void OLED_WriteCommand(uint8_t Command)
{
    uint8_t write_buffer[2];
    
    // 构造写命令的缓冲区：控制字节(0x00) + 命令字节
    write_buffer[0] = 0x00;        // 控制字节，表示命令
    write_buffer[1] = Command;     // 命令字节
    
    esp_err_t ret = i2c_master_write_to_device(I2C_NUM_1, 
                                              OLED_ADDR, 
                                              write_buffer, 
                                              sizeof(write_buffer), 
                                              pdMS_TO_TICKS(1000));
    
    if (ret != ESP_OK) {
        ESP_LOGE("OLED", "Write command 0x%02X failed: %s", Command, esp_err_to_name(ret));
    }
}

/**
  * 函    数：OLED写数据
  * 参    数：Data 要写入数据的起始地址
  * 参    数：Count 要写入数据的数量
  * 返 回 值：无
  */
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
    if (Count == 0) {
        return; // 没有数据要写，直接返回
    }
    
    // 分配内存：控制字节(0x40) + 数据
    uint8_t *write_buffer = (uint8_t *)malloc(Count + 1);
    if (write_buffer == NULL) {
        ESP_LOGE("OLED", "Memory allocation failed for WriteData.");
        return;
    }
    
    // 构造写数据的缓冲区
    write_buffer[0] = 0x40;        // 控制字节，表示数据
    memcpy(&write_buffer[1], Data, Count); // 复制数据
    
    esp_err_t ret = i2c_master_write_to_device(I2C_NUM_1, 
                                              OLED_ADDR, 
                                              write_buffer, 
                                              Count + 1, 
                                              pdMS_TO_TICKS(1000));
    
    free(write_buffer); // 释放内存
    
    if (ret != ESP_OK) {
        ESP_LOGE("OLED", "Write data failed: %s", esp_err_to_name(ret));
    }
}
/*********************通信协议*/

/*硬件配置*********************/

/**
  * 函    数：OLED初始化
  * 参    数：port  I2C端口号
  * 参    数：add   oled地址
  * 参    数：scl   SCL引脚
  * 参    数：sda   SDA引脚
  * 参    数：speed SCL时钟频率(Hz)，不应大于400k
  * 返 回 值：无
  * 说    明：使用前，需要调用此初始化函数
  */

  
void OLED_Init()
{
  int i2c_master_port = I2C_NUM_1;

  i2c_config_t conf = {
      .mode = I2C_MODE_MASTER,
      .sda_io_num = OLED_SDA,
      .scl_io_num = OLED_SCL,
      .sda_pullup_en = GPIO_PULLUP_ENABLE,
      .scl_pullup_en = GPIO_PULLUP_ENABLE,
      .master.clk_speed = OLED_SPEED,
  };

  esp_err_t ret=i2c_param_config(i2c_master_port, &conf);

  if (ret != ESP_OK) {
        ESP_LOGE("I2C", "i2c_param_config failed: %s", esp_err_to_name(ret));
        return;
    }

  ret=i2c_driver_install(i2c_master_port, conf.mode, 0, 0, 0);
  if (ret != ESP_OK) {
        ESP_LOGE("I2C", "i2c_driver_install failed: %s", esp_err_to_name(ret));
        return;
    }
    
    ESP_LOGI("I2C", "I2C2 master initialized successfully");
    

  OLED_WriteCommand(0xAE);	//设置显示开启/关闭，0xAE关闭，0xAF开启
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);	//0x00-0xFF
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x1F);	//0x0E-0x3F
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);	//0x00-0x7F
	
	OLED_WriteCommand(0x40);	//设置显示开始行，0x40-0x7F
	
	OLED_WriteCommand(0xA1);	//设置左右方向，0xA1正常，0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向，0xC8正常，0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x02);
	
	OLED_WriteCommand(0x81);	//设置对比度
	OLED_WriteCommand(0xCF);	//0x00-0xFF

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/反色显示，0xA6正常，0xA7反色

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
	
	OLED_Clear();				//清空显存数组
	OLED_Update();				//更新显示，清屏，防止初始化后未显示内容时花屏
	cursortop=0;
}

void OLED_DrawBar(uint8_t top,uint8_t Y){
	uint8_t tmp=cursortop;
	if (tmp==top)return;
	if (tmp>top){
		for (;tmp>top;--tmp){
			OLED_DisplayBuf[Y*OLED_WIDTH+tmp]^=0xFF;
		}
	}
	else {
		for (++tmp;tmp<=top;++tmp){
			OLED_DisplayBuf[Y*OLED_WIDTH+tmp]^=0xFF;
		}
	}
	cursortop=top;
	return ;
}


void OLED_DrawLine(uint8_t X,uint8_t Y){
  OLED_DisplayBuf[Y*OLED_WIDTH+X]^=0xFF;
}

/**
  * 函    数：OLED设置显示光标位置
  * 参    数：Page 指定光标所在的页，范围：0-7
  * 参    数：X 指定光标所在的X轴坐标，范围：0-127
  * 返 回 值：无
  * 说    明：OLED默认的Y轴，只能8个Bit为一组写入，即1页等于8个Y轴坐标
  */
void OLED_SetCursor(uint8_t Page, uint8_t X)
{
	/*如果使用此程序驱动1.3寸的OLED显示屏，则需要解除此注释*/
	/*因为1.3寸的OLED驱动芯片（SH1106）有132列*/
	/*屏幕的起始列接在了第2列，而不是第0列*/
	/*所以需要将X加2，才能正常显示*/
//	X += 2;
	
	/*通过指令设置页地址和列地址*/
	OLED_WriteCommand(0xB0 | Page);					//设置页位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/*********************硬件配置*/

/*工具函数*********************/

/*工具函数仅供内部部分函数使用*/

/**
  * 函    数：次方函数
  * 参    数：X 底数
  * 参    数：Y 指数
  * 返 回 值：等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//结果默认为1
	while (Y --)			//累乘Y次
	{
		Result *= X;		//每次把X累乘到结果上
	}
	return Result;
}







/*功能函数*********************/

/**
  * 函    数：将OLED显存数组更新到OLED屏幕
  * 参    数：无
  * 返 回 值：无
  * 说    明：所有的显示函数，都只是对OLED显存数组进行读写
  *           随后调用OLED_Update函数或OLED_UpdateArea函数
  *           才会将显存数组的数据发送到OLED硬件，进行显示
  *           故调用显示函数后，要想真正地呈现在屏幕上，还需调用更新函数
  */
void OLED_Update(void)
{
	uint8_t j;
	/*遍历每一页*/
	for (j = 0; j < 4; j ++)
	{
		/*设置光标位置为每一页的第一列*/
		OLED_SetCursor(j, 0);
		/*连续写入128个数据，将显存数组的数据写入到OLED硬件*/
		OLED_WriteData(&OLED_DisplayBuf[j*OLED_WIDTH], OLED_WIDTH);
	}
}


/**
  * 函    数：将OLED显存数组全部清零
  * 参    数：无
  * 返 回 值：无
  * 说    明：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
  */
void OLED_Clear(void)
{
	uint8_t i, j;
	for (j = 0; j < 4; j ++)				//遍历8页
	{
		for (i = 0; i < 128; i ++)			//遍历128列
		{
			OLED_DisplayBuf[j*OLED_WIDTH+i] = 0x00;	//将显存数组数据全部清零
		}
	}
	cursortop=0;
}



/**
  * 函    数：将OLED显存数组全部取反
  * 参    数：无
  * 返 回 值：无
  * 说    明：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
  */
void OLED_Reverse(void)
{
	uint8_t i, j;
	for (j = 0; j < 4; j ++)				//遍历8页
	{
		for (i = 0; i < 128; i ++)			//遍历128列
		{
			OLED_DisplayBuf[j*OLED_WIDTH+i] ^= 0xFF;	//将显存数组数据全部取反
		}
	}
}
	



/*****************江协科技|版权所有****************/
/*****************jiangxiekeji.com*****************/
